//***********************************************************************************************//
/// \file     uclog.c
/// \version  0.1.0
/// \brief    UCLOG is the library that implements application-level logging. This library 
///           provides logging APIs based on C stdio's functions and various helper macros. 
/// \author   Krystian Rebas
/// \details  Sorry, no user manual, learn from the code ;p
///           For more details about UCLOG usage see uclog.h/cfg files.
/// \note     Copyright (C) 2012 by Krystian Rebas.
///           All rights reserved.
///
///           Redistribution and use in source and binary forms, with or without
///           modification, are permitted provided that the following conditions are met: 
///           
///           1. Redistributions of source code must retain the above copyright notice, this
///           list of conditions and the following disclaimer. 
///           2. Redistributions in binary form must reproduce the above copyright notice,
///           this list of conditions and the following disclaimer in the documentation
///           and/or other materials provided with the distribution. 
///
///           THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
///           ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
///           WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
///           DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
///           ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
///           (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
///           LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
///           ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
///           (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
///           SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
///
//***********************************************************************************************//

//***********************************************************************************************//
//* doxygen data ///////////////////////////////////////////////////////////////// doxygen data *//
//***********************************************************************************************//

/// \addtogroup UCLOG uclog
/// @{


//***********************************************************************************************//
//* includes ///////////////////////////////////////////////////////////////////////// includes *//
//***********************************************************************************************//

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#include "uclog.h"

#if (UCLOG_ENABLED)

//***********************************************************************************************//
//* defines, macros /////////////////////////////////////////////////////////// defines, macros *//
//***********************************************************************************************//

#ifdef _MSC_VER
//M$ does not provide C99-compilant implementation of (v)snprintf...
#if _MSC_VER >= 1300
// LOG supports only Visual C++ 7.0 or above (with scprintf function)
#undef snprintf
#undef vsnprintf
#define _CRT_SECURE_NO_WARNINGS 1
#define snprintf c99_snprintf
#define vsnprintf c99_vsnprintf
int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)
{
  int count = -1;

  if (size != 0)
  {
    count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
  }
  if (count == -1)
  {
    count = _vscprintf(format, ap);
  }

  return count;
}
int c99_snprintf(char* str, size_t size, const char* format, ...)
{
    int count;
    va_list args;
    va_start(args, format);
    count = c99_vsnprintf(str, size, format, args);
    va_end(args);
    return count;
}
#else
#error Visual C++ 6.0 or below not supported!
#endif
#endif // _MSC_VER


//***********************************************************************************************//
//* typedefs, enums /////////////////////////////////////////////////////////// typedefs, enums *//
//***********************************************************************************************//
typedef enum 
{
  UCLOG_SV_INFO,
  UCLOG_SV_WARNING,
  UCLOG_SV_ERROR,
  UCLOG_SV_FATAL,
  UCLOG_SV_DEBUG,
  UCLOG_SEVERITIES_NUMBER
} UCLOG_severity;

//***********************************************************************************************//
//* private function prototypes /////////////////////////////////// private function prototypes *//
//***********************************************************************************************//
static void UCLOG_print
(
  UCLOG_severity severity, ///< LOG severity marker to display.
  char* fs,   ///< Format string, same like for printf function.
  ...
);

//***********************************************************************************************//
//* variables /////////////////////////////////////////////////////////////////////// variables *//
//***********************************************************************************************//

//-----------------------------------------------------------------------------------------------//
// global variables /////////////////////////////////////////////////////////// global variables //
//-----------------------------------------------------------------------------------------------//
#if (UCLOG_MODE == USE_MEM_BUFFER)
char UCLOG_MemBuffer[UCLOG_BUFFER_SIZE];
#endif


// names are configured in UCLOG_CFG_FILE
static const char* const UCLOG_SeverityNames[UCLOG_SEVERITIES_NUMBER] = 
{
  UCLOG_SEVERITY_INFO, 
  UCLOG_SEVERITY_WARNING, 
  UCLOG_SEVERITY_ERROR, 
  UCLOG_SEVERITY_FATAL
};


#if (UCLOG_MODE == USE_MEM_BUFFER)
static char* UCLOG_MemBufferPtr = UCLOG_MemBuffer;
#endif

//***********************************************************************************************//
//* function definitions ///////////////////////////////////////////////// function definitions *//
//***********************************************************************************************//

#if (UCLOG_USE_THREAD_SAFE_PRINT)
// UCLOG Library callback API functions used for OS thread-safe log printing
extern VOID UCLOG_takeSemaphore(void);
extern VOID UCLOG_giveSemaphore(void);
#endif


#if (UCLOG_USE_TIMESTAMPS)
// UCLOG Library callback API function used for gathering timestamps used in log printing
extern ULONG UCLOG_getTimeStamp(void);
#endif

//-----------------------------------------------------------------------------------------------//
// exported functions /////////////////////////////////////////////////////// exported functions //
//-----------------------------------------------------------------------------------------------//
#ifdef UCLOG_SEVERITY_INFO
//FN******************************************************************************************** //
/// \brief    Logs message of severity INFO.
/// \author   Krystian Rebas
/// \details  Calls UCLOG_printf with given severity ID.
//***********************************************************************************************//
void UCLOG_printInfo
(
  char* fs,   ///< format string, same like for printf function
  ...
)
{
  va_list args;
  va_start (args, fs);
  UCLOG_print(UCLOG_SV_INFO, fs, args);
  va_end(args);
}
#endif //UCLOG_SEVERITY_INFO

#ifdef UCLOG_SEVERITY_WARNING
//FN******************************************************************************************** //
/// \brief    Logs message of severity WARNING
/// \author   Krystian Rebas
/// \details  Calls UCLOG_printf with given severity ID.
//***********************************************************************************************//
void UCLOG_printWarning
(
  char* fs,   ///< format string, same like for printf function
  ...
)
{
  va_list args;
  va_start (args, fs);
  UCLOG_print(UCLOG_SV_WARNING, fs, args);
  va_end(args);
}
#endif //UCLOG_SEVERITY_WARNING

#ifdef UCLOG_SEVERITY_ERROR
//FN******************************************************************************************** //
/// \brief    Log messages of severity ERROR
/// \author   Krystian Rebas
/// \details  Calls UCLOG_printf with given severity ID.
//***********************************************************************************************//
void UCLOG_printError
(
  char* fs,   ///< format string, same like for printf function
  ...
)
{
  va_list args;
  va_start (args, fs);
  UCLOG_print(UCLOG_SV_ERROR, fs, args);
  va_end(args);
}
#endif //UCLOG_SEVERITY_ERROR

#ifdef UCLOG_SEVERITY_FATAL
//FN******************************************************************************************** //
/// \brief    Logs message of severity FATAL after which program terminates / freezes.
/// \author   Krystian Rebas
/// \details  Calls UCLOG_printf with given severity ID.
/// \note     Funtion may never return - see UCLOG_FATAL_BEHAVIOR description.
//***********************************************************************************************//
void UCLOG_printFatal
(
  char* fs,   ///< format string, same like for printf function
  ...
)
{
  va_list args;
  va_start (args, fs);
  UCLOG_print(UCLOG_SV_FATAL, fs, args);
  va_end(args);
}
#endif //UCLOG_SEVERITY_FATAL

#ifdef UCLOG_SEVERITY_DEBUG
//FN******************************************************************************************** //
/// \brief    Logs message of severity DEBUG.
/// \author   Krystian Rebas
/// \details  This kind of messages are logged only if UCLOG_USE_SEVERITY_DEBUG and DEBUG are
///           defined. First one must be defined in UCLOG_CFG_FILE, second is auromatically
///           defined in most developing environments while debug release was choosed. This 
///           is very useful for quick disable/enable special logging for debug releases only.
//***********************************************************************************************//
void UCLOG_printDebug
(
  char* fs,   ///< format string, same like for printf function
  ...
)
{
  va_list args;
  va_start (args, fs);
  UCLOG_print(UCLOG_SV_DEBUG, fs, args);
  va_end(args);
}
#endif //UCLOG_SEVERITY_DEBUG

//-----------------------------------------------------------------------------------------------//
// private functions ///////////////////////////////////////////////////////// private functions //
//-----------------------------------------------------------------------------------------------//

//FN******************************************************************************************** //
/// \brief    Logs message of gvien severity ID
/// \author   Krystian Rebas
/// \details  Generic log output function.
/// \note     Funtion may never return if severity is FATAL - see UCLOG_FATAL_BEHAVIOR description.
//***********************************************************************************************//
static void UCLOG_print
(
  UCLOG_severity severity, ///< LOG severity marker to display
  char* fs,   ///< format string, same like for printf function
  va_list args  ///< va_list with variadic arguments
)
{
#if (UCLOG_MODE == USE_MEM_BUFFER)
  size_t avail_buff_size;
#endif
#if (UCLOG_USE_THREAD_SAFE_PRINT)
  UCLOG_takeSemaphore();
#endif
#if (UCLOG_MODE == USE_MEM_BUFFER)
  //each call of LOG starts on new line
  avail_buff_size = sizeof(UCLOG_MemBuffer) - (UCLOG_MemBufferPtr - UCLOG_MemBuffer);
  if (avail_buff_size > 0)
  {
    UCLOG_MemBufferPtr += snprintf(UCLOG_MemBufferPtr, avail_buff_size, "\n");
  }
#if (UCLOG_USE_TIMESTAMPS)
  //log timestamp
  avail_buff_size = sizeof(UCLOG_MemBuffer) - (UCLOG_MemBufferPtr - UCLOG_MemBuffer);
  if (avail_buff_size > 0)
  {
    UCLOG_MemBufferPtr += sprintf(UCLOG_MemBufferPtr, avail_buff_size, "%07lu| ", UCLOG_getTimeStamp());
  }
#endif
  //log severity marker
  avail_buff_size = sizeof(UCLOG_MemBuffer) - (UCLOG_MemBufferPtr - UCLOG_MemBuffer);
  if (avail_buff_size > 0)
  {
    if (severity >= UCLOG_SV_INFO && severity <= UCLOG_SV_FATAL)
    {
        UCLOG_MemBufferPtr += snprintf(UCLOG_MemBufferPtr, avail_buff_size, "%s", UCLOG_SeverityNames[severity]);
    }
  } 
  //log user data
  avail_buff_size = sizeof(UCLOG_MemBuffer) - (UCLOG_MemBufferPtr - UCLOG_MemBuffer);
  if (avail_buff_size > 0)
  {
    UCLOG_MemBufferPtr += vsnprintf(UCLOG_MemBufferPtr, avail_buff_size, fs, args);
  } 
#else //UCLOG_MODE != USE_MEM_BUFFER
  //each call of LOG starts on new line
  printf("\n");
#if (UCLOG_USE_TIMESTAMPS)
  //log timestamp
  printf("%07lu| ", UCLOG_getTimeStamp());
#endif
  //log severity marker
  if (severity >= UCLOG_SV_INFO && severity <= UCLOG_SV_FATAL)
  {
      printf("%s", UCLOG_SeverityNames[severity]);
  }
  //log user data
  vprintf(fs, args);
#endif //UCLOG_MODE != USE_MEM_BUFFER
#if (UCLOG_USE_THREAD_SAFE_PRINT)
  UCLOG_giveSemaphore();
#endif 
  va_end(args);
  if (UCLOG_SV_FATAL == severity)
  {
#if (UCLOG_FATAL_BEHAVIOR == 1)
    //use system exit
    exit(1);
#elif (UCLOG_FATAL_BEHAVIOR == 2)
    //infinite loop, let the watchdog do the rest...
    for(;;);
#endif
  }
}


#endif //UCLOG_ENABLED

/// @}
//EOF



